/*
 * Copyright 2001-2015 Haiku, inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Jerome Duval
 *		Erik Jaesler, erik@cgsoftware.com
 */


#include <Application.h>

#include <new>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <Alert.h>
#include <AppFileInfo.h>
#include <Cursor.h>
#include <Debug.h>
#include <Entry.h>
#include <File.h>
#include <Locker.h>
#include <MessageRunner.h>
#include <ObjectList.h>
#include <Path.h>
#include <PropertyInfo.h>
#include <RegistrarDefs.h>
#include <Resources.h>
#include <Roster.h>
#include <Window.h>

#include <AppMisc.h>
#include <AppServerLink.h>
#include <AutoLocker.h>
#include <BitmapPrivate.h>
#include <DraggerPrivate.h>
#include <LaunchDaemonDefs.h>
#include <LaunchRoster.h>
#include <LooperList.h>
#include <MenuWindow.h>
#include <PicturePrivate.h>
#include <PortLink.h>
#include <RosterPrivate.h>
#include <ServerMemoryAllocator.h>
#include <ServerProtocol.h>


using namespace BPrivate;


static const char* kDefaultLooperName = "AppLooperPort";

BApplication* be_app = NULL;
BMessenger be_app_messenger;

pthread_once_t sAppResourcesInitOnce = PTHREAD_ONCE_INIT;
BResources* BApplication::sAppResources = NULL;
BObjectList<BLooper> sOnQuitLooperList;


enum {
	kWindowByIndex,
	kWindowByName,
	kLooperByIndex,
	kLooperByID,
	kLooperByName,
	kApplication
};


static property_info sPropertyInfo[] = {
	{
		"Window",
		{},
		{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
		NULL, kWindowByIndex,
		{},
		{},
		{}
	},
	{
		"Window",
		{},
		{B_NAME_SPECIFIER},
		NULL, kWindowByName,
		{},
		{},
		{}
	},
	{
		"Looper",
		{},
		{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
		NULL, kLooperByIndex,
		{},
		{},
		{}
	},
	{
		"Looper",
		{},
		{B_ID_SPECIFIER},
		NULL, kLooperByID,
		{},
		{},
		{}
	},
	{
		"Looper",
		{},
		{B_NAME_SPECIFIER},
		NULL, kLooperByName,
		{},
		{},
		{}
	},
	{
		"Name",
		{B_GET_PROPERTY},
		{B_DIRECT_SPECIFIER},
		NULL, kApplication,
		{B_STRING_TYPE},
		{},
		{}
	},
	{
		"Window",
		{B_COUNT_PROPERTIES},
		{B_DIRECT_SPECIFIER},
		NULL, kApplication,
		{B_INT32_TYPE},
		{},
		{}
	},
	{
		"Loopers",
		{B_GET_PROPERTY},
		{B_DIRECT_SPECIFIER},
		NULL, kApplication,
		{B_MESSENGER_TYPE},
		{},
		{}
	},
	{
		"Windows",
		{B_GET_PROPERTY},
		{B_DIRECT_SPECIFIER},
		NULL, kApplication,
		{B_MESSENGER_TYPE},
		{},
		{}
	},
	{
		"Looper",
		{B_COUNT_PROPERTIES},
		{B_DIRECT_SPECIFIER},
		NULL, kApplication,
		{B_INT32_TYPE},
		{},
		{}
	},

	{ 0 }
};


// argc/argv
extern const int __libc_argc;
extern const char* const *__libc_argv;


// debugging
//#define DBG(x) x
#define DBG(x)
#define OUT	printf


//	#pragma mark - static helper functions


/*!
	\brief Checks whether the supplied string is a valid application signature.

	An error message is printed, if the string is no valid app signature.

	\param signature The string to be checked.

	\return A status code.
	\retval B_OK \a signature is a valid app signature.
	\retval B_BAD_VALUE \a signature is \c NULL or no valid app signature.
*/
static status_t
check_app_signature(const char* signature)
{
	bool isValid = false;
	BMimeType type(signature);

	if (type.IsValid() && !type.IsSupertypeOnly()
		&& BMimeType("application").Contains(&type)) {
		isValid = true;
	}

	if (!isValid) {
		printf("bad signature (%s), must begin with \"application/\" and "
			   "can't conflict with existing registered mime types inside "
			   "the \"application\" media type.\n", signature);
	}

	return (isValid ? B_OK : B_BAD_VALUE);
}


#ifndef RUN_WITHOUT_REGISTRAR
// Fills the passed BMessage with B_ARGV_RECEIVED infos.
static void
fill_argv_message(BMessage &message)
{
	message.what = B_ARGV_RECEIVED;

	int32 argc = __libc_argc;
	const char* const *argv = __libc_argv;

	// add argc
	message.AddInt32("argc", argc);

	// add argv
	for (int32 i = 0; i < argc; i++) {
		if (argv[i] != NULL)
			message.AddString("argv", argv[i]);
	}

	// add current working directory
	char cwd[B_PATH_NAME_LENGTH];
	if (getcwd(cwd, B_PATH_NAME_LENGTH))
		message.AddString("cwd", cwd);
}
#endif


//	#pragma mark - BApplication


BApplication::BApplication(const char* signature)
	:
	BLooper(kDefaultLooperName)
{
	_InitData(signature, true, NULL);
}


BApplication::BApplication(const char* signature, status_t* _error)
	:
	BLooper(kDefaultLooperName)
{
	_InitData(signature, true, _error);
}


BApplication::BApplication(const char* signature, const char* looperName,
	port_id port, bool initGUI, status_t* _error)
	:
	BLooper(B_NORMAL_PRIORITY + 1, port < 0 ? _GetPort(signature) : port,
		looperName != NULL ? looperName : kDefaultLooperName)
{
	_InitData(signature, initGUI, _error);
	if (port < 0)
		fOwnsPort = false;
}


BApplication::BApplication(BMessage* data)
	// Note: BeOS calls the private BLooper(int32, port_id, const char*)
	// constructor here, test if it's needed
	:
	BLooper(kDefaultLooperName)
{
	const char* signature = NULL;
	data->FindString("mime_sig", &signature);

	_InitData(signature, true, NULL);

	bigtime_t pulseRate;
	if (data->FindInt64("_pulse", &pulseRate) == B_OK)
		SetPulseRate(pulseRate);
}


#ifdef _BEOS_R5_COMPATIBLE_
BApplication::BApplication(uint32 signature)
{
}


BApplication::BApplication(const BApplication &rhs)
{
}


BApplication&
BApplication::operator=(const BApplication &rhs)
{
	return *this;
}
#endif


BApplication::~BApplication()
{
	Lock();

	// tell all loopers(usually windows) to quit. Also, wait for them.
	_QuitAllWindows(true);

	// quit registered loopers
	for (int32 i = 0; i < sOnQuitLooperList.CountItems(); i++) {
		BLooper* looper = sOnQuitLooperList.ItemAt(i);
		if (looper->Lock())
			looper->Quit();
	}

	// unregister from the roster
	BRoster::Private().RemoveApp(Team());

#ifndef RUN_WITHOUT_APP_SERVER
	// tell app_server we're quitting...
	if (be_app) {
		// be_app can be NULL here if the application fails to initialize
		// correctly. For example, if it's already running and it's set to
		// exclusive launch.
		BPrivate::AppServerLink link;
		link.StartMessage(B_QUIT_REQUESTED);
		link.Flush();
	}
	delete_port(fServerLink->SenderPort());
	delete_port(fServerLink->ReceiverPort());
	delete fServerLink;
#endif	// RUN_WITHOUT_APP_SERVER

	delete fServerAllocator;

	// uninitialize be_app, the be_app_messenger is invalidated automatically
	be_app = NULL;
}


void
BApplication::_InitData(const char* signature, bool initGUI, status_t* _error)
{
	DBG(OUT("BApplication::InitData(`%s', %p)\n", signature, _error));
	// check whether there exists already an application
	if (be_app != NULL)
		debugger("2 BApplication objects were created. Only one is allowed.");

	fServerLink = new BPrivate::PortLink(-1, -1);
	fServerAllocator = NULL;
	fServerReadOnlyMemory = NULL;
	fInitialWorkspace = 0;
	//fDraggedMessage = NULL;
	fReadyToRunCalled = false;

	// initially, there is no pulse
	fPulseRunner = NULL;
	fPulseRate = 0;

	// check signature
	fInitError = check_app_signature(signature);
	fAppName = signature;

#ifndef RUN_WITHOUT_REGISTRAR
	bool registerApp = signature == NULL
		|| (strcasecmp(signature, B_REGISTRAR_SIGNATURE) != 0
			&& strcasecmp(signature, kLaunchDaemonSignature) != 0);
	// get team and thread
	team_id team = Team();
	thread_id thread = BPrivate::main_thread_for(team);
#endif

	// get app executable ref
	entry_ref ref;
	if (fInitError == B_OK) {
		fInitError = BPrivate::get_app_ref(&ref);
		if (fInitError != B_OK) {
			DBG(OUT("BApplication::InitData(): Failed to get app ref: %s\n",
				strerror(fInitError)));
		}
	}

	// get the BAppFileInfo and extract the information we need
	uint32 appFlags = B_REG_DEFAULT_APP_FLAGS;
	if (fInitError == B_OK) {
		BAppFileInfo fileInfo;
		BFile file(&ref, B_READ_ONLY);
		fInitError = fileInfo.SetTo(&file);
		if (fInitError == B_OK) {
			fileInfo.GetAppFlags(&appFlags);
			char appFileSignature[B_MIME_TYPE_LENGTH];
			// compare the file signature and the supplied signature
			if (fileInfo.GetSignature(appFileSignature) == B_OK
				&& strcasecmp(appFileSignature, signature) != 0) {
				printf("Signature in rsrc doesn't match constructor arg. (%s, %s)\n",
					signature, appFileSignature);
			}
		} else {
			DBG(OUT("BApplication::InitData(): Failed to get info from: "
				"BAppFileInfo: %s\n", strerror(fInitError)));
		}
	}

#ifndef RUN_WITHOUT_REGISTRAR
	// check whether be_roster is valid
	if (fInitError == B_OK && registerApp
		&& !BRoster::Private().IsMessengerValid(false)) {
		printf("FATAL: be_roster is not valid. Is the registrar running?\n");
		fInitError = B_NO_INIT;
	}

	// check whether or not we are pre-registered
	bool preRegistered = false;
	app_info appInfo;
	if (fInitError == B_OK && registerApp) {
		if (BRoster::Private().IsAppRegistered(&ref, team, 0, &preRegistered,
				&appInfo) != B_OK) {
			preRegistered = false;
		}
	}
	if (preRegistered) {
		// we are pre-registered => the app info has been filled in
		// Check whether we need to replace the looper port with a port
		// created by the roster.
		if (appInfo.port >= 0 && appInfo.port != fMsgPort) {
			delete_port(fMsgPort);
			fMsgPort = appInfo.port;
		} else
			appInfo.port = fMsgPort;
		// check the signature and correct it, if necessary, also the case
		if (strcmp(appInfo.signature, fAppName))
			BRoster::Private().SetSignature(team, fAppName);
		// complete the registration
		fInitError = BRoster::Private().CompleteRegistration(team, thread,
						appInfo.port);
	} else if (fInitError == B_OK) {
		// not pre-registered -- try to register the application
		team_id otherTeam = -1;
		if (registerApp) {
			fInitError = BRoster::Private().AddApplication(signature, &ref,
				appFlags, team, thread, fMsgPort, true, NULL, &otherTeam);
			if (fInitError != B_OK) {
				DBG(OUT("BApplication::InitData(): Failed to add app: %s\n",
					strerror(fInitError)));
			}
		}
		if (fInitError == B_ALREADY_RUNNING) {
			// An instance is already running and we asked for
			// single/exclusive launch. Send our argv to the running app.
			// Do that only, if the app is NOT B_ARGV_ONLY.
			if (otherTeam >= 0) {
				BMessenger otherApp(NULL, otherTeam);
				app_info otherAppInfo;
				bool argvOnly = be_roster->GetRunningAppInfo(otherTeam,
						&otherAppInfo) == B_OK
					&& (otherAppInfo.flags & B_ARGV_ONLY) != 0;

				if (__libc_argc > 1 && !argvOnly) {
					// create an B_ARGV_RECEIVED message
					BMessage argvMessage(B_ARGV_RECEIVED);
					fill_argv_message(argvMessage);

					// replace the first argv string with the path of the
					// other application
					BPath path;
					if (path.SetTo(&otherAppInfo.ref) == B_OK)
						argvMessage.ReplaceString("argv", 0, path.Path());

					// send the message
					otherApp.SendMessage(&argvMessage);
				} else if (!argvOnly)
					otherApp.SendMessage(B_SILENT_RELAUNCH);
			}
		} else if (fInitError == B_OK) {
			// the registrations was successful
			// Create a B_ARGV_RECEIVED message and send it to ourselves.
			// Do that even, if we are B_ARGV_ONLY.
			// TODO: When BLooper::AddMessage() is done, use that instead of
			// PostMessage().

			DBG(OUT("info: BApplication successfully registered.\n"));

			if (__libc_argc > 1) {
				BMessage argvMessage(B_ARGV_RECEIVED);
				fill_argv_message(argvMessage);
				PostMessage(&argvMessage, this);
			}
			// send a B_READY_TO_RUN message as well
			PostMessage(B_READY_TO_RUN, this);
		} else if (fInitError > B_ERRORS_END) {
			// Registrar internal errors shouldn't fall into the user's hands.
			fInitError = B_ERROR;
		}
	}
#else
	// We need to have ReadyToRun called even when we're not using the registrar
	PostMessage(B_READY_TO_RUN, this);
#endif	// ifndef RUN_WITHOUT_REGISTRAR

	if (fInitError == B_OK) {
		// TODO: Not completely sure about the order, but this should be close.

		// init be_app and be_app_messenger
		be_app = this;
		be_app_messenger = BMessenger(NULL, this);

		// set the BHandler's name
		SetName(ref.name);

		// create meta MIME
		BPath path;
		if (registerApp && path.SetTo(&ref) == B_OK)
			create_app_meta_mime(path.Path(), false, true, false);

#ifndef RUN_WITHOUT_APP_SERVER
		// app server connection and IK initialization
		if (initGUI)
			fInitError = _InitGUIContext();
#endif	// RUN_WITHOUT_APP_SERVER
	}

	// Return the error or exit, if there was an error and no error variable
	// has been supplied.
	if (_error != NULL) {
		*_error = fInitError;
	} else if (fInitError != B_OK) {
		DBG(OUT("BApplication::InitData() failed: %s\n", strerror(fInitError)));
		exit(0);
	}
DBG(OUT("BApplication::InitData() done\n"));
}


port_id
BApplication::_GetPort(const char* signature)
{
	return BLaunchRoster().GetPort(signature, NULL);
}


BArchivable*
BApplication::Instantiate(BMessage* data)
{
	if (validate_instantiation(data, "BApplication"))
		return new BApplication(data);

	return NULL;
}


status_t
BApplication::Archive(BMessage* data, bool deep) const
{
	status_t status = BLooper::Archive(data, deep);
	if (status < B_OK)
		return status;

	app_info info;
	status = GetAppInfo(&info);
	if (status < B_OK)
		return status;

	status = data->AddString("mime_sig", info.signature);
	if (status < B_OK)
		return status;

	return data->AddInt64("_pulse", fPulseRate);
}


status_t
BApplication::InitCheck() const
{
	return fInitError;
}


thread_id
BApplication::Run()
{
	if (fInitError != B_OK)
		return fInitError;

	Loop();

	delete fPulseRunner;
	return fThread;
}


void
BApplication::Quit()
{
	bool unlock = false;
	if (!IsLocked()) {
		const char* name = Name();
		if (name == NULL)
			name = "no-name";

		printf("ERROR - you must Lock the application object before calling "
			   "Quit(), team=%" B_PRId32 ", looper=%s\n", Team(), name);
		unlock = true;
		if (!Lock())
			return;
	}
	// Delete the object, if not running only.
	if (!fRunCalled) {
		delete this;
	} else if (find_thread(NULL) != fThread) {
// ToDo: why shouldn't we set fTerminating to true directly in this case?
		// We are not the looper thread.
		// We push a _QUIT_ into the queue.
		// TODO: When BLooper::AddMessage() is done, use that instead of
		// PostMessage()??? This would overtake messages that are still at
		// the port.
		// NOTE: We must not unlock here -- otherwise we had to re-lock, which
		// may not work. This is bad, since, if the port is full, it
		// won't get emptier, as the looper thread needs to lock the object
		// before dispatching messages.
		while (PostMessage(_QUIT_, this) == B_WOULD_BLOCK)
			snooze(10000);
	} else {
		// We are the looper thread.
		// Just set fTerminating to true which makes us fall through the
		// message dispatching loop and return from Run().
		fTerminating = true;
	}

	// If we had to lock the object, unlock now.
	if (unlock)
		Unlock();
}


bool
BApplication::QuitRequested()
{
	return _QuitAllWindows(false);
}


void
BApplication::Pulse()
{
	// supposed to be implemented by subclasses
}


void
BApplication::ReadyToRun()
{
	// supposed to be implemented by subclasses
}


void
BApplication::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_COUNT_PROPERTIES:
		case B_GET_PROPERTY:
		case B_SET_PROPERTY:
		{
			int32 index;
			BMessage specifier;
			int32 what;
			const char* property = NULL;
			if (message->GetCurrentSpecifier(&index, &specifier, &what,
					&property) < B_OK
				|| !ScriptReceived(message, index, &specifier, what,
					property)) {
				BLooper::MessageReceived(message);
			}
			break;
		}

		case B_SILENT_RELAUNCH:
			// Sent to a B_SINGLE_LAUNCH application when it's launched again
			// (see _InitData())
			be_roster->ActivateApp(Team());
			break;

		case kMsgAppServerRestarted:
			_ReconnectToServer();
			break;

		case kMsgDeleteServerMemoryArea:
		{
			int32 serverArea;
			if (message->FindInt32("server area", &serverArea) == B_OK) {
				// The link is not used, but we currently borrow its lock
				BPrivate::AppServerLink link;
				fServerAllocator->RemoveArea(serverArea);
			}
			break;
		}

		default:
			BLooper::MessageReceived(message);
	}
}


void
BApplication::ArgvReceived(int32 argc, char** argv)
{
	// supposed to be implemented by subclasses
}


void
BApplication::AppActivated(bool active)
{
	// supposed to be implemented by subclasses
}


void
BApplication::RefsReceived(BMessage* message)
{
	// supposed to be implemented by subclasses
}


void
BApplication::AboutRequested()
{
	// supposed to be implemented by subclasses
}


BHandler*
BApplication::ResolveSpecifier(BMessage* message, int32 index,
	BMessage* specifier, int32 what, const char* property)
{
	BPropertyInfo propInfo(sPropertyInfo);
	status_t err = B_OK;
	uint32 data;

	if (propInfo.FindMatch(message, 0, specifier, what, property, &data) >= 0) {
		switch (data) {
			case kWindowByIndex:
			{
				int32 index;
				err = specifier->FindInt32("index", &index);
				if (err != B_OK)
					break;

				if (what == B_REVERSE_INDEX_SPECIFIER)
					index = CountWindows() - index;

				BWindow* window = WindowAt(index);
				if (window != NULL) {
					message->PopSpecifier();
					BMessenger(window).SendMessage(message);
				} else
					err = B_BAD_INDEX;
				break;
			}

			case kWindowByName:
			{
				const char* name;
				err = specifier->FindString("name", &name);
				if (err != B_OK)
					break;

				for (int32 i = 0;; i++) {
					BWindow* window = WindowAt(i);
					if (window == NULL) {
						err = B_NAME_NOT_FOUND;
						break;
					}
					if (window->Title() != NULL && !strcmp(window->Title(),
							name)) {
						message->PopSpecifier();
						BMessenger(window).SendMessage(message);
						break;
					}
				}
				break;
			}

			case kLooperByIndex:
			{
				int32 index;
				err = specifier->FindInt32("index", &index);
				if (err != B_OK)
					break;

				if (what == B_REVERSE_INDEX_SPECIFIER)
					index = CountLoopers() - index;

				BLooper* looper = LooperAt(index);
				if (looper != NULL) {
					message->PopSpecifier();
					BMessenger(looper).SendMessage(message);
				} else
					err = B_BAD_INDEX;

				break;
			}

			case kLooperByID:
				// TODO: implement getting looper by ID!
				break;

			case kLooperByName:
			{
				const char* name;
				err = specifier->FindString("name", &name);
				if (err != B_OK)
					break;

				for (int32 i = 0;; i++) {
					BLooper* looper = LooperAt(i);
					if (looper == NULL) {
						err = B_NAME_NOT_FOUND;
						break;
					}
					if (looper->Name() != NULL
						&& strcmp(looper->Name(), name) == 0) {
						message->PopSpecifier();
						BMessenger(looper).SendMessage(message);
						break;
					}
				}
				break;
			}

			case kApplication:
				return this;
		}
	} else {
		return BLooper::ResolveSpecifier(message, index, specifier, what,
			property);
	}

	if (err != B_OK) {
		BMessage reply(B_MESSAGE_NOT_UNDERSTOOD);
		reply.AddInt32("error", err);
		reply.AddString("message", strerror(err));
		message->SendReply(&reply);
	}

	return NULL;

}


void
BApplication::ShowCursor()
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SHOW_CURSOR);
	link.Flush();
}


void
BApplication::HideCursor()
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_HIDE_CURSOR);
	link.Flush();
}


void
BApplication::ObscureCursor()
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_OBSCURE_CURSOR);
	link.Flush();
}


bool
BApplication::IsCursorHidden() const
{
	BPrivate::AppServerLink link;
	int32 status = B_ERROR;
	link.StartMessage(AS_QUERY_CURSOR_HIDDEN);
	link.FlushWithReply(status);

	return status == B_OK;
}


void
BApplication::SetCursor(const void* cursorData)
{
	BCursor cursor(cursorData);
	SetCursor(&cursor, true);
		// forces the cursor to be sync'ed
}


void
BApplication::SetCursor(const BCursor* cursor, bool sync)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_CURSOR);
	link.Attach<bool>(sync);
	link.Attach<int32>(cursor->fServerToken);

	if (sync) {
		int32 code;
		link.FlushWithReply(code);
	} else
		link.Flush();
}


int32
BApplication::CountWindows() const
{
	return _CountWindows(false);
		// we're ignoring menu windows
}


BWindow*
BApplication::WindowAt(int32 index) const
{
	return _WindowAt(index, false);
		// we're ignoring menu windows
}


int32
BApplication::CountLoopers() const
{
	AutoLocker<BLooperList> ListLock(gLooperList);
	if (ListLock.IsLocked())
		return gLooperList.CountLoopers();

	// Some bad, non-specific thing has happened
	return B_ERROR;
}


BLooper*
BApplication::LooperAt(int32 index) const
{
	BLooper* looper = NULL;
	AutoLocker<BLooperList> listLock(gLooperList);
	if (listLock.IsLocked())
		looper = gLooperList.LooperAt(index);

	return looper;
}


status_t
BApplication::RegisterLooper(BLooper* looper)
{
	BWindow* window = dynamic_cast<BWindow*>(looper);
	if (window != NULL)
		return B_BAD_VALUE;

	if (sOnQuitLooperList.HasItem(looper))
		return B_ERROR;

	if (sOnQuitLooperList.AddItem(looper) != true)
		return B_ERROR;

	return B_OK;
}


status_t
BApplication::UnregisterLooper(BLooper* looper)
{
	BWindow* window = dynamic_cast<BWindow*>(looper);
	if (window != NULL)
		return B_BAD_VALUE;

	if (!sOnQuitLooperList.HasItem(looper))
		return B_ERROR;

	if (sOnQuitLooperList.RemoveItem(looper) != true)
		return B_ERROR;

	return B_OK;
}


bool
BApplication::IsLaunching() const
{
	return !fReadyToRunCalled;
}


const char*
BApplication::Signature() const
{
	return fAppName;
}


status_t
BApplication::GetAppInfo(app_info* info) const
{
	if (be_app == NULL || be_roster == NULL)
		return B_NO_INIT;
	return be_roster->GetRunningAppInfo(be_app->Team(), info);
}


BResources*
BApplication::AppResources()
{
	if (sAppResources == NULL)
		pthread_once(&sAppResourcesInitOnce, &_InitAppResources);

	return sAppResources;
}


void
BApplication::DispatchMessage(BMessage* message, BHandler* handler)
{
	if (handler != this) {
		// it's not ours to dispatch
		BLooper::DispatchMessage(message, handler);
		return;
	}

	switch (message->what) {
		case B_ARGV_RECEIVED:
			_ArgvReceived(message);
			break;

		case B_REFS_RECEIVED:
		{
			// this adds the refs that are part of this message to the recent
			// lists, but only folders and documents are handled here
			entry_ref ref;
			int32 i = 0;
			while (message->FindRef("refs", i++, &ref) == B_OK) {
				BEntry entry(&ref, true);
				if (entry.InitCheck() != B_OK)
					continue;

				if (entry.IsDirectory())
					BRoster().AddToRecentFolders(&ref);
				else {
					// filter out applications, we only want to have documents
					// in the recent files list
					BNode node(&entry);
					BNodeInfo info(&node);

					char mimeType[B_MIME_TYPE_LENGTH];
					if (info.GetType(mimeType) != B_OK
						|| strcasecmp(mimeType, B_APP_MIME_TYPE))
						BRoster().AddToRecentDocuments(&ref);
				}
			}

			RefsReceived(message);
			break;
		}

		case B_READY_TO_RUN:
			if (!fReadyToRunCalled) {
				ReadyToRun();
				fReadyToRunCalled = true;
			}
			break;

		case B_ABOUT_REQUESTED:
			AboutRequested();
			break;

		case B_PULSE:
			Pulse();
			break;

		case B_APP_ACTIVATED:
		{
			bool active;
			if (message->FindBool("active", &active) == B_OK)
				AppActivated(active);
			break;
		}

		case B_COLORS_UPDATED:
		{
			AutoLocker<BLooperList> listLock(gLooperList);
			if (!listLock.IsLocked())
				break;

			BWindow* window = NULL;
			uint32 count = gLooperList.CountLoopers();
			for (uint32 index = 0; index < count; ++index) {
				window = dynamic_cast<BWindow*>(gLooperList.LooperAt(index));
				if (window == NULL || (window != NULL && window->fOffscreen))
					continue;
				window->PostMessage(message);
			}
			break;
		}

		case _SHOW_DRAG_HANDLES_:
		{
			bool show;
			if (message->FindBool("show", &show) != B_OK)
				break;

			BDragger::Private::UpdateShowAllDraggers(show);
			break;
		}

		// TODO: Handle these as well
		case _DISPOSE_DRAG_:
		case _PING_:
			puts("not yet handled message:");
			DBG(message->PrintToStream());
			break;

		default:
			BLooper::DispatchMessage(message, handler);
			break;
	}
}


void
BApplication::SetPulseRate(bigtime_t rate)
{
	if (rate < 0)
		rate = 0;

	// BeBook states that we have only 100,000 microseconds granularity
	rate -= rate % 100000;

	if (!Lock())
		return;

	if (rate != 0) {
		// reset existing pulse runner, or create new one
		if (fPulseRunner == NULL) {
			BMessage pulse(B_PULSE);
			fPulseRunner = new BMessageRunner(be_app_messenger, &pulse, rate);
		} else
			fPulseRunner->SetInterval(rate);
	} else {
		// turn off pulse messages
		delete fPulseRunner;
		fPulseRunner = NULL;
	}

	fPulseRate = rate;
	Unlock();
}


status_t
BApplication::GetSupportedSuites(BMessage* data)
{
	if (data == NULL)
		return B_BAD_VALUE;

	status_t status = data->AddString("suites", "suite/vnd.Be-application");
	if (status == B_OK) {
		BPropertyInfo propertyInfo(sPropertyInfo);
		status = data->AddFlat("messages", &propertyInfo);
		if (status == B_OK)
			status = BLooper::GetSupportedSuites(data);
	}

	return status;
}


status_t
BApplication::Perform(perform_code d, void* arg)
{
	return BLooper::Perform(d, arg);
}


void BApplication::_ReservedApplication1() {}
void BApplication::_ReservedApplication2() {}
void BApplication::_ReservedApplication3() {}
void BApplication::_ReservedApplication4() {}
void BApplication::_ReservedApplication5() {}
void BApplication::_ReservedApplication6() {}
void BApplication::_ReservedApplication7() {}
void BApplication::_ReservedApplication8() {}


bool
BApplication::ScriptReceived(BMessage* message, int32 index,
	BMessage* specifier, int32 what, const char* property)
{
	BMessage reply(B_REPLY);
	status_t err = B_BAD_SCRIPT_SYNTAX;

	switch (message->what) {
		case B_GET_PROPERTY:
			if (strcmp("Loopers", property) == 0) {
				int32 count = CountLoopers();
				err = B_OK;
				for (int32 i=0; err == B_OK && i<count; i++) {
					BMessenger messenger(LooperAt(i));
					err = reply.AddMessenger("result", messenger);
				}
			} else if (strcmp("Windows", property) == 0) {
				int32 count = CountWindows();
				err = B_OK;
				for (int32 i=0; err == B_OK && i<count; i++) {
					BMessenger messenger(WindowAt(i));
					err = reply.AddMessenger("result", messenger);
				}
			} else if (strcmp("Window", property) == 0) {
				switch (what) {
					case B_INDEX_SPECIFIER:
					case B_REVERSE_INDEX_SPECIFIER:
					{
						int32 index = -1;
						err = specifier->FindInt32("index", &index);
						if (err != B_OK)
							break;

						if (what == B_REVERSE_INDEX_SPECIFIER)
							index = CountWindows() - index;

						err = B_BAD_INDEX;
						BWindow* window = WindowAt(index);
						if (window == NULL)
							break;

						BMessenger messenger(window);
						err = reply.AddMessenger("result", messenger);
						break;
					}

					case B_NAME_SPECIFIER:
					{
						const char* name;
						err = specifier->FindString("name", &name);
						if (err != B_OK)
							break;
						err = B_NAME_NOT_FOUND;
						for (int32 i = 0; i < CountWindows(); i++) {
							BWindow* window = WindowAt(i);
							if (window && window->Name() != NULL
								&& !strcmp(window->Name(), name)) {
								BMessenger messenger(window);
								err = reply.AddMessenger("result", messenger);
								break;
							}
						}
						break;
					}
				}
			} else if (strcmp("Looper", property) == 0) {
				switch (what) {
					case B_INDEX_SPECIFIER:
					case B_REVERSE_INDEX_SPECIFIER:
					{
						int32 index = -1;
						err = specifier->FindInt32("index", &index);
						if (err != B_OK)
							break;

						if (what == B_REVERSE_INDEX_SPECIFIER)
							index = CountLoopers() - index;

						err = B_BAD_INDEX;
						BLooper* looper = LooperAt(index);
						if (looper == NULL)
							break;

						BMessenger messenger(looper);
						err = reply.AddMessenger("result", messenger);
						break;
					}

					case B_NAME_SPECIFIER:
					{
						const char* name;
						err = specifier->FindString("name", &name);
						if (err != B_OK)
							break;
						err = B_NAME_NOT_FOUND;
						for (int32 i = 0; i < CountLoopers(); i++) {
							BLooper* looper = LooperAt(i);
							if (looper != NULL && looper->Name()
								&& strcmp(looper->Name(), name) == 0) {
								BMessenger messenger(looper);
								err = reply.AddMessenger("result", messenger);
								break;
							}
						}
						break;
					}

					case B_ID_SPECIFIER:
					{
						// TODO
						debug_printf("Looper's ID specifier used but not "
							"implemented.\n");
						break;
					}
				}
			} else if (strcmp("Name", property) == 0)
				err = reply.AddString("result", Name());

			break;

		case B_COUNT_PROPERTIES:
			if (strcmp("Looper", property) == 0)
				err = reply.AddInt32("result", CountLoopers());
			else if (strcmp("Window", property) == 0)
				err = reply.AddInt32("result", CountWindows());

			break;
	}
	if (err == B_BAD_SCRIPT_SYNTAX)
		return false;

	if (err < B_OK) {
		reply.what = B_MESSAGE_NOT_UNDERSTOOD;
		reply.AddString("message", strerror(err));
	}
	reply.AddInt32("error", err);
	message->SendReply(&reply);

	return true;
}


void
BApplication::BeginRectTracking(BRect rect, bool trackWhole)
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_BEGIN_RECT_TRACKING);
	link.Attach<BRect>(rect);
	link.Attach<int32>(trackWhole);
	link.Flush();
}


void
BApplication::EndRectTracking()
{
	BPrivate::AppServerLink link;
	link.StartMessage(AS_END_RECT_TRACKING);
	link.Flush();
}


status_t
BApplication::_SetupServerAllocator()
{
	fServerAllocator = new (std::nothrow) BPrivate::ServerMemoryAllocator();
	if (fServerAllocator == NULL)
		return B_NO_MEMORY;

	return fServerAllocator->InitCheck();
}


status_t
BApplication::_InitGUIContext()
{
	// An app_server connection is necessary for a lot of stuff, so get that first.
	status_t error = _ConnectToServer();
	if (error != B_OK)
		return error;

	// Initialize the IK after we have set be_app because of a construction
	// of a AppServerLink (which depends on be_app) nested inside the call
	// to get_menu_info.
	error = _init_interface_kit_();
	if (error != B_OK)
		return error;

	// create global system cursors
	B_CURSOR_SYSTEM_DEFAULT = new BCursor(B_HAND_CURSOR);
	B_CURSOR_I_BEAM = new BCursor(B_I_BEAM_CURSOR);

	// TODO: would be nice to get the workspace at launch time from the registrar
	fInitialWorkspace = current_workspace();

	return B_OK;
}


status_t
BApplication::_ConnectToServer()
{
	status_t status
		= create_desktop_connection(fServerLink, "a<app_server", 100);
	if (status != B_OK)
		return status;

	// AS_CREATE_APP:
	//
	// Attach data:
	// 1) port_id - receiver port of a regular app
	// 2) port_id - looper port for this BApplication
	// 3) team_id - team identification field
	// 4) int32 - handler ID token of the app
	// 5) char* - signature of the regular app

	fServerLink->StartMessage(AS_CREATE_APP);
	fServerLink->Attach<port_id>(fServerLink->ReceiverPort());
	fServerLink->Attach<port_id>(_get_looper_port_(this));
	fServerLink->Attach<team_id>(Team());
	fServerLink->Attach<int32>(_get_object_token_(this));
	fServerLink->AttachString(fAppName);

	area_id sharedReadOnlyArea;
	team_id serverTeam;
	port_id serverPort;

	int32 code;
	if (fServerLink->FlushWithReply(code) == B_OK
		&& code == B_OK) {
		// We don't need to contact the main app_server anymore
		// directly; we now talk to our server alter ego only.
		fServerLink->Read<port_id>(&serverPort);
		fServerLink->Read<area_id>(&sharedReadOnlyArea);
		fServerLink->Read<team_id>(&serverTeam);
	} else {
		fServerLink->SetSenderPort(-1);
		debugger("BApplication: couldn't obtain new app_server comm port");
		return B_ERROR;
	}
	fServerLink->SetTargetTeam(serverTeam);
	fServerLink->SetSenderPort(serverPort);

	status = _SetupServerAllocator();
	if (status != B_OK)
		return status;

	area_id area;
	uint8* base;
	status = fServerAllocator->AddArea(sharedReadOnlyArea, area, base, true);
	if (status < B_OK)
		return status;

	fServerReadOnlyMemory = base;

	return B_OK;
}


void
BApplication::_ReconnectToServer()
{
	delete_port(fServerLink->SenderPort());
	delete_port(fServerLink->ReceiverPort());

	if (_ConnectToServer() != B_OK)
		debugger("Can't reconnect to app server!");

	AutoLocker<BLooperList> listLock(gLooperList);
	if (!listLock.IsLocked())
		return;

	uint32 count = gLooperList.CountLoopers();
	for (uint32 i = 0; i < count ; i++) {
		BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
		if (window == NULL)
			continue;
		BMessenger windowMessenger(window);
		windowMessenger.SendMessage(kMsgAppServerRestarted);
	}

	reconnect_bitmaps_to_app_server();
	reconnect_pictures_to_app_server();
}


#if 0
void
BApplication::send_drag(BMessage* message, int32 vs_token, BPoint offset,
	BRect dragRect, BHandler* replyTo)
{
	// TODO: implement
}


void
BApplication::send_drag(BMessage* message, int32 vs_token, BPoint offset,
	int32 bitmapToken, drawing_mode dragMode, BHandler* replyTo)
{
	// TODO: implement
}


void
BApplication::write_drag(_BSession_* session, BMessage* message)
{
	// TODO: implement
}
#endif


bool
BApplication::_WindowQuitLoop(bool quitFilePanels, bool force)
{
	int32 index = 0;
	while (true) {
		 BWindow* window = WindowAt(index);
		 if (window == NULL)
		 	break;

		// NOTE: the window pointer might be stale, in case the looper
		// was already quit by quitting an earlier looper... but fortunately,
		// we can still call Lock() on the invalid pointer, and it
		// will return false...
		if (!window->Lock())
			continue;

		// don't quit file panels if we haven't been asked for it
		if (!quitFilePanels && window->IsFilePanel()) {
			window->Unlock();
			index++;
			continue;
		}

		if (!force && !window->QuitRequested()
			&& !(quitFilePanels && window->IsFilePanel())) {
			// the window does not want to quit, so we don't either
			window->Unlock();
			return false;
		}

		// Re-lock, just to make sure that the user hasn't done nasty
		// things in QuitRequested(). Quit() unlocks fully, thus
		// double-locking is harmless.
		if (window->Lock())
			window->Quit();

		index = 0;
			// we need to continue at the start of the list again - it
			// might have changed
	}

	return true;
}


bool
BApplication::_QuitAllWindows(bool force)
{
	AssertLocked();

	// We need to unlock here because BWindow::QuitRequested() must be
	// allowed to lock the application - which would cause a deadlock
	Unlock();

	bool quit = _WindowQuitLoop(false, force);
	if (quit)
		quit = _WindowQuitLoop(true, force);

	Lock();

	return quit;
}


void
BApplication::_ArgvReceived(BMessage* message)
{
	ASSERT(message != NULL);

	// build the argv vector
	status_t error = B_OK;
	int32 argc = 0;
	char** argv = NULL;
	if (message->FindInt32("argc", &argc) == B_OK && argc > 0) {
		// allocate a NULL terminated array
		argv = new(std::nothrow) char*[argc + 1];
		if (argv == NULL)
			return;

		// copy the arguments
		for (int32 i = 0; error == B_OK && i < argc; i++) {
			const char* arg = NULL;
			error = message->FindString("argv", i, &arg);
			if (error == B_OK && arg) {
				argv[i] = strdup(arg);
				if (argv[i] == NULL)
					error = B_NO_MEMORY;
			} else
				argc = i;
		}

		argv[argc] = NULL;
	}

	// call the hook
	if (error == B_OK && argc > 0)
		ArgvReceived(argc, argv);

	if (error != B_OK) {
		printf("Error parsing B_ARGV_RECEIVED message. Message:\n");
		message->PrintToStream();
	}

	// cleanup
	if (argv) {
		for (int32 i = 0; i < argc; i++)
			free(argv[i]);
		delete[] argv;
	}
}


uint32
BApplication::InitialWorkspace()
{
	return fInitialWorkspace;
}


int32
BApplication::_CountWindows(bool includeMenus) const
{
	uint32 count = 0;
	for (int32 i = 0; i < gLooperList.CountLoopers(); i++) {
		BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
		if (window != NULL && !window->fOffscreen && (includeMenus
				|| dynamic_cast<BMenuWindow*>(window) == NULL)) {
			count++;
		}
	}

	return count;
}


BWindow*
BApplication::_WindowAt(uint32 index, bool includeMenus) const
{
	AutoLocker<BLooperList> listLock(gLooperList);
	if (!listLock.IsLocked())
		return NULL;

	uint32 count = gLooperList.CountLoopers();
	for (uint32 i = 0; i < count && index < count; i++) {
		BWindow* window = dynamic_cast<BWindow*>(gLooperList.LooperAt(i));
		if (window == NULL || (window != NULL && window->fOffscreen)
			|| (!includeMenus && dynamic_cast<BMenuWindow*>(window) != NULL)) {
			index++;
			continue;
		}

		if (i == index)
			return window;
	}

	return NULL;
}


/*static*/ void
BApplication::_InitAppResources()
{
	entry_ref ref;
	bool found = false;

	// App is already running. Get its entry ref with
	// GetAppInfo()
	app_info appInfo;
	if (be_app && be_app->GetAppInfo(&appInfo) == B_OK) {
		ref = appInfo.ref;
		found = true;
	} else {
		// Run() hasn't been called yet
		found = BPrivate::get_app_ref(&ref) == B_OK;
	}

	if (!found)
		return;

	BFile file(&ref, B_READ_ONLY);
	if (file.InitCheck() != B_OK)
		return;

	BResources* resources = new (std::nothrow) BResources(&file, false);
	if (resources == NULL || resources->InitCheck() != B_OK) {
		delete resources;
		return;
	}

	sAppResources = resources;
}
